# Define default settings for compiler options, build type, etc.
include(CMakeDependentOption)
include(CMakeParseArguments)

macro(nosal_defaults)

    # Set our preferred CMAKE_BUILD_TYPE.
    #
    # You can not simply use set(CMAKE_BUILD_TYPE ... CACHE ...) as CMake actually defaults
    # to a defined (but empty) value for CMAKE_BUILD_TYPE. Thus the override never takes
    # effect.
    #
    if(NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
            "Choose the type of build, one of: Debug, Release, RelWithDebInfo, MinSizeRel"
            FORCE)
    endif()

    # We always want these flags enabled.
    # If any project really needs to disable one of these, they can do so by adding
    # the -Wno-* equivalent of these to their target's attributes, which then overrides
    # the earlier given flag.
    list(APPEND _nosal_common_warning_flags
        -Wall
        -Wcast-align            # a pointer is cast such that the required alignment of the target is increased
        -Wcast-qual             # a pointer is cast so as to remove a type qualifier
        -Wdate-time             # warn when certain macros are encountered that prevent reproducible builds
        -Wdeprecated
        -Wdouble-promotion      # a "float" is implicitly promoted to "double"
        -Wextra
        -Wfloat-equal           # floating-point values are used in equality comparisons
        -Wformat=2              # check printf format strings
        -Wmissing-declarations  # a global function is defined without a previous declaration (probably missing "static")
        -Wmissing-include-dirs  # a user-supplied include directory does not exist
        -Wpedantic
        -Wredundant-decls       # anything is declared more than once in the same scope
        -Wshadow                # a local variable or type declaration shadows another variable, parameter, type, class member, ...
        -Wswitch-default        # a "switch" statement does not have a "default" case
        -Wundef                 # an undefined identifier is evaluated in an #if directive
        -Wuninitialized         # an automatic variable is used without first being initialized
        -Wunused                # an unused parameter, variable, function, label, local typedef, value is encountered
        )

    list(APPEND _nosal_c_only_warning_flags
        -Wconversion            # implicit conversions that may alter a value
        -Winit-self             # an uninitialized variable is initialized with itselves
        -Wwrite-strings         # give string constants the type "const char[length]", and warn about loss of constness when copying
        -Wsign-conversion       # implicit conversions that may change the sign of an integer value
        -Wstrict-prototypes     # a function is declared or defined without specifying the argument types
        )

    list(APPEND _nosal_cxx_only_warning_flags
        -Wctor-dtor-privacy     # a class seems unusable because all the constructors or destructors in that class are private
        -Wnon-virtual-dtor      # class has virtual functions and an accessible non-virtual destructor
        -Wold-style-cast        # an old-style (C-style) cast to a non-void type is used
        -Woverloaded-virtual    # a function declaration hides virtual functions from a base class
        -Wsign-promo            # overload resolution chooses a promotion from unsigned type to a signed type
        )

    #set(c_and_gnu "$<AND:$<COMPILE_LANGUAGE:C>,$<C_COMPILER_ID:GNU>>")
    #set(cxx_and_gnu "$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:GNU>>")
    #set(c_or_cxx_and_gnu "$<OR:${c_and_gnu},${cxx_and_gnu}>")

    #set(c_and_clang "$<AND:$<COMPILE_LANGUAGE:C>,$<C_COMPILER_ID:Clang>>")
    #set(cxx_and_clang "$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:Clang>>")
    #set(c_or_cxx_and_clang "$<OR:${c_and_clang},${cxx_and_clang}>")

    #set(_cxx_like_gnu "$<OR:$<CXX_COMPILER_ID:GNU>,$<CXX_COMPILER_ID:Clang>>")
    #set(cxx_and_like_gnu "$<AND:$<COMPILE_LANGUAGE:CXX>,${_cxx_like_gnu}>")

    #set(_c_like_gnu "$<OR:$<C_COMPILER_ID:GNU>,$<C_COMPILER_ID:Clang>>")
    #set(c_and_like_gnu "$<AND:$<COMPILE_LANGUAGE:C>,${_c_like_gnu}>")

    #set(c_or_cxx_and_like_gnu "$<OR:${c_and_like_gnu},${cxx_and_like_gnu}>")

    #add_compile_options("$<${c_or_cxx_and_like_gnu}:${_nosal_common_warning_flags}>")
    #add_compile_options("$<${c_and_like_gnu}:${_nosal_c_only_warning_flags}>")
    #add_compile_options("$<${cxx_and_like_gnu}:${_nosal_cxx_only_warning_flags}>")

    ## Add some GCC-specific warning options
    #add_compile_options("$<${c_or_cxx_and_gnu}:-Wsuggest-attribute=format>")
    #add_compile_options("$<${c_or_cxx_and_gnu}:-Wsuggest-attribute=noreturn>")

    #unset(_nosal_cxx_only_warning_flags)
    #unset(_nosal_c_only_warning_flags)
    #unset(_nosal_common_warning_flags)

    ## Parts of our code base are known to violate the Strict Aliasing Rule.
    #add_compile_options("$<${c_or_cxx_and_like_gnu}:-fno-strict-aliasing>")

    ## This is needed for full standards-compliant handling of static destructors.
    #add_compile_options("$<${cxx_and_like_gnu}:-fuse-cxa-atexit>")

    ## Let gcc emit extra code to check for buffer overflows, using a stack canary.
    #add_compile_options("$<${c_or_cxx_and_gnu}:-fstack-protector-strong>")

    ## Set full RELRO: make relocations section read-only, and avoid lazy binding.
    #add_link_options("$<${c_or_cxx_and_like_gnu}:-Wl,-z,relro,-z,now>")

    add_compile_options("${_nosal_common_warning_flags}")
    add_compile_options("${_nosal_c_only_warning_flags}")
    add_compile_options("${_nosal_cxx_only_warning_flags}")

    unset(_nosal_cxx_only_warning_flags)
    unset(_nosal_c_only_warning_flags)
    unset(_nosal_common_warning_flags)


    # Standard settings
    #
    #   Disable GCC extensions (this is consistent with specifying -Wpedantic above)
    #   Define our preferred language variants
    #
    # We do not use the CACHE for these, as we do not want them overridden from the
    # command line.

    set(CMAKE_CXX_EXTENSIONS OFF)
    set(CMAKE_CXX_STANDARD 14)

    set(CMAKE_C_EXTENSIONS OFF)
    set(CMAKE_C_STANDARD 99)

    # When linking against Threads::Threads, we generally want to use -pthread for both compiling and linking.
    # This needs to be set before the first call to find_package(Threads), so we best do it here.
    set(THREADS_PREFER_PTHREAD_FLAG ON)

    # Introduce option for building the unit test executable by default.
    #
    # However, running the unit tests using docker or building
    # the documentation make target shall be user-defined
    option(NOSAL_ENABLE_TESTS "Enable unit testing" ON)
    option(NOSAL_RUN_TESTS "Enable running unit testing" OFF)
    option(NOSAL_ENABLE_DOXYGEN "Enables the documentation target" OFF)

    # Testing / Debugging
    cmake_dependent_option(NOSAL_ENABLE_DEBUG "Enable debug for docker target" OFF
        "NOSAL_RUN_TESTS" OFF)
    message(STATUS "option NOSAL_ENABLE_TESTS=" ${NOSAL_ENABLE_TESTS})
    message(STATUS "option NOSAL_RUN_TESTS=" ${NOSAL_RUN_TESTS})
    message(STATUS "option NOSAL_ENABLE_DEBUG=" ${NOSAL_ENABLE_DEBUG} " (depends on NOSAL_RUN_TESTS)")

    if(NOSAL_ENABLE_TESTS)
        set(CMAKE_CXX_EXTENSIONS ON)
    endif()

    if(NOSAL_RUN_TESTS)
        #include(NOSAL/sa8155)
        #sa8155_download()
        #sa8155_runtest()
    endif()

    # NOSAL Applications are installed under the /opt directory (typically
    # /opt/${PROJECT_NAME}/bin and /opt/${PROJECT_NAME}/etc) and this is
    # where Execution Manager looks for them.
    #
    # This means we need to use absolute paths: the implicit
    # ${CMAKE_INSTALL_PREFIX}/opt won't work.
    #
    # To avoid hard-coding this into every CMake script which installs an
    # application, we define the path here. we use the CACHE to allow it
    # to be overridden from the command line or from Yocto (via OECMAKE_EXTRA).
    #
    # Apps then install to ${NOSAL_APP_INSTALL_PREFIX}/${PROJECT_NAME}/bin
    #
    set(NOSAL_APP_INSTALL_PREFIX /opt CACHE PATH "Default installation root for applications")

    # An NOSAL extension to GNUInstallDirs
    set(NOSAL_CMAKE_INSTALL_BINTESTDIR "bintest" CACHE PATH "Standard installation path for unit tests")

endmacro()


# Define a library, suitable for Library
# Install an NOSAL library binary to the correct place in the image
#
# lib_name is the name of library target
#
function(nosal_add_library lib_name)
    set(multiValueArgs SOURCES LIBS INCLUDES DEFINITIONS OPTIONS)
    cmake_parse_arguments(_lib "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    if(NOT _lib_SOURCES)
        message(FATAL_ERROR "Error adding test ${test_name}: No SOURCES specified.")
    endif()

    # Define library target
    add_library(${lib_name} ${_lib_SOURCES})
    target_include_directories(${lib_name} PRIVATE ${_lib_INCLUDES}
	    PUBLIC $<INSTALL_INTERFACE:include> )
    target_compile_options(${lib_name} PRIVATE ${_lib_OPTIONS})
    target_compile_definitions(${lib_name} PRIVATE ${_lib_DEFINITIONS})
    target_link_libraries(${lib_name} PRIVATE ${_lib_LIBS})

    add_library(nosal::${lib_name} ALIAS ${lib_name})
    set_target_properties(${lib_name}
        PROPERTIES
        SOVERSION ${PROJECT_VERSION_MAJOR}
        VERSION ${PROJECT_VERSION}
        OUTPUT_NAME nosal-${lib_name}
    )

    # Install exported targets, i.e. *-targets.cmake
    install(
        TARGETS ${lib_name}
        EXPORT ${lib_name}-targets
        ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
        LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
        RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    )
    install(
        EXPORT ${lib_name}-targets
        DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/nosal-${lib_name}
        NAMESPACE nosal::
    )

    # Install header files
    install(
        DIRECTORY include/
        DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
        COMPONENT dev
        FILES_MATCHING PATTERN "*.h"
    )

    include(FindPkgConfig)
    if(PKG_CONFIG_FOUND)
        configure_file(${PROJECT_SOURCE_DIR}/files/pkgconfig/${lib_name}.pc.in nosal-${lib_name}.pc @ONLY)
        install(FILES ${CMAKE_CURRENT_BINARY_DIR}/nosal-${lib_name}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
    endif()

    # Create *-config.cmake file
    include(CMakePackageConfigHelpers)
    configure_package_config_file(
        ${PROJECT_SOURCE_DIR}/files/cmake/${lib_name}-config.cmake.in
        ${CMAKE_CURRENT_BINARY_DIR}/nosal-${lib_name}-config.cmake
        INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/nosal-${lib_name}
        NO_SET_AND_CHECK_MACRO
    )
    # Create *-config-version.cmake file
    write_basic_package_version_file(
        ${CMAKE_CURRENT_BINARY_DIR}/nosal-${lib_name}-config-version.cmake
        COMPATIBILITY ExactVersion
    )
    # Install *-config.cmake and *-version.cmake files
    install(
        FILES
            ${CMAKE_CURRENT_BINARY_DIR}/nosal-${lib_name}-config.cmake
            ${CMAKE_CURRENT_BINARY_DIR}/nosal-${lib_name}-config-version.cmake
        DESTINATION
            ${CMAKE_INSTALL_LIBDIR}/cmake/nosal-${lib_name}
    )

endfunction()

# Define a test, suitable for execution with CTest, either natively, or via Docker
#
# test_name is the name of test target (and is expected to equal its filename)
#
function(nosal_add_test test_name)
    set(multiValueArgs SOURCES LIBS INCLUDES DEFINITIONS OPTIONS)
    cmake_parse_arguments(_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    if(NOT _test_SOURCES)
        message(FATAL_ERROR "Error adding test ${test_name}: No SOURCES specified.")
    endif()

    # Define test target
    add_executable(${test_name} ${_test_SOURCES})
    target_include_directories(${test_name} PRIVATE ${_test_INCLUDES})
    target_compile_options(${test_name} PRIVATE ${_test_OPTIONS})
    target_compile_definitions(${test_name} PRIVATE ${_test_DEFINITIONS})
    target_link_libraries(${test_name} PRIVATE ${_test_LIBS}
	    gtest
	    #${GMOCK}
	    #GTest::Main
	    #Threads::Threads
    )

    if(NOSAL_RUN_TESTS)
	message(STATUS "Adding Sa8155-based test ${test_name}")
	#sa8155_exec_test(${test_name} ${CMAKE_CURRENT_BINARY_DIR})
    else()
        message(STATUS "Adding test ${test_name}")
        add_test(NAME ${test_name} COMMAND ${test_name})
    endif()

    # Install test
    install(TARGETS ${test_name}
            DESTINATION ${NOSAL_CMAKE_INSTALL_BINTESTDIR}
            COMPONENT test
            OPTIONAL
    )
endfunction()

# Define a application, suitable for execution
#
# app_name is the name of application target (and is expected to equal its filename)
#
function(nosal_add_application app_name)
    set(multiValueArgs SOURCES LIBS INCLUDES DEFINITIONS OPTIONS)
    cmake_parse_arguments(_app "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    if(NOT _app_SOURCES)
        message(FATAL_ERROR "Error adding app ${app_name}: No SOURCES specified.")
    endif()

    # Define app target
    add_executable(${app_name} ${_app_SOURCES})
    target_include_directories(${app_name} PRIVATE ${_app_INCLUDES})
    target_compile_options(${app_name} PRIVATE ${_app_OPTIONS})
    target_compile_definitions(${app_name} PRIVATE ${_app_DEFINITIONS})
    target_link_libraries(${app_name} PRIVATE ${_app_LIBS})

    # Install application
    install(TARGETS ${app_name}
            RUNTIME
            DESTINATION ${NOSAL_APP_INSTALL_PREFIX}/${app_name}/bin
    )

    install(FILES files/manifest.json
            DESTINATION ${NOSAL_APP_INSTALL_PREFIX}/${app_name}/etc
            RENAME manifest.json
            PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
    )
endfunction()


# Install an NOSAL application executable to the correct place in the image
#
# NOSAL_APP_NAME is the directory name to use under /opt
# NOSAL_TARGET is the target name used in the add_executable call
#
function(nosal_install_app_executable NOSAL_APP_NAME NOSAL_TARGET)
    install(TARGETS ${NOSAL_TARGET}
            RUNTIME
            DESTINATION ${NOSAL_APP_INSTALL_PREFIX}/${NOSAL_APP_NAME}/bin
    )
endfunction()

# Install an NOSAL application manifest to the correct place in the image
#
# NOSAL_APP_NAME is the directory name to use under /opt
# MANIFEST_FILE is the name of the json file to be installed
#
# The MANIFEST file will always be installed as manifest.json
#
function(nosal_install_app_execution_manifest NOSAL_APP_NAME MANIFEST_FILE)
    install(FILES ${MANIFEST_FILE}
            DESTINATION ${NOSAL_APP_INSTALL_PREFIX}/${NOSAL_APP_NAME}/etc
            RENAME manifest.json
            PERMISSIONS OWNER_READ GROUP_READ WORLD_READ
    )
endfunction()

# Install an additional file for an NOSAL application
#
# Install one or more files associated with an NOSAL application
#
# NOSAL_APP_NAME is the directory name to use under /opt
#
# DIR_NAME is the (bare) name of the subdirectory below /opt. Typical values
# are "var" "run" or "vsomeip"
#
# FILE_NAME is the path to one or more files to be installed. If more than one
# file name is supplied, it must be in the form of a CMake list. This can be achieved
# by simply enclosing the list in quotes, e.g.
#
#     nosal_install_app_file( MyApp var "file1 file2 file3")
#
function(nosal_install_app_file NOSAL_APP_NAME DIR_NAME FILE_NAME)
    install(FILES ${FILE_NAME}
            DESTINATION ${NOSAL_APP_INSTALL_PREFIX}/${NOSAL_APP_NAME}/${DIR_NAME}
    )
endfunction()

# Install an additional file for an NOSAL application and rename it
#
# Install one file associated with an NOSAL application and rename it. This can because
# used to install generated files with "strange" filenames using a simpler name.
#
# NOSAL_APP_NAME is the directory name to use under /opt
#
# DIR_NAME is the (bare) name of the subdirectory below /opt. Typical values
# are "var" "run" or "vsomeip"
#
# FILE_NAME is the path to one files to be installed.
#
# RENAME_NAME is the new name of the file.
#
function(nosal_install_app_file_rename NOSAL_APP_NAME DIR_NAME FILE_NAME RENAME_NAME)
    install(FILES ${FILE_NAME}
            DESTINATION ${NOSAL_APP_INSTALL_PREFIX}/${NOSAL_APP_NAME}/${DIR_NAME}
            RENAME ${RENAME_NAME}
    )
endfunction()

# Everyone needs the default settings

nosal_defaults()
